﻿package idv.cjcat.emitter {
	use namespace engine;
	
	import flash.errors.*;
	import flash.utils.*;
	import flash.xml.XMLNode;
	
	import idv.cjcat.emitter.behaviors.*;
	import idv.cjcat.emitter.deflectors.*;
	import idv.cjcat.emitter.fields.*;
	import idv.cjcat.emitter.errors.*;
	
	/**
	 * This class provides interface to some global-scale operations on elements (particles, particle sources, fields, deflectors, behavior triggers, and behaviors).
	 */
	public class EmitterGlobal {
		
		//reference holders
		/** @private */
		engine static var _particles:Dictionary = new Dictionary();
		/** @private */
		engine static var _sources:Dictionary = new Dictionary();
		/** @private */
		engine static var _emitters:Dictionary = new Dictionary();
		/** @private */
		engine static var _fields:Dictionary = new Dictionary();
		/** @private */
		engine static var _deflectors:Dictionary = new Dictionary();
		/** @private */
		engine static var _behaviorTriggers:Dictionary = new Dictionary();
		/** @private */
		engine static var _behaviors:Dictionary = new Dictionary();
		
		//counters
		/** @private */
		engine static var _particleCounter:uint = 0;
		/** @private */
		engine static var _sourceCounter:uint = 0;
		/** @private */
		engine static var _emitterCounter:uint = 0;
		/** @private */
		engine static var _fieldCounter:uint = 0;
		/** @private */
		engine static var _deflectorCounter:uint = 0;
		/** @private */
		engine static var _behaviorTriggerCounter:uint = 0;
		/** @private */
		engine static var _behaviorCounter:uint = 0;
		
		//xml parser related
		/** @private */
		engine static var _registeredSources:Vector.<ClassRegistryEntry>;
		/** @private */
		engine static var _registeredFields:Vector.<ClassRegistryEntry>;
		/** @private */
		engine static var _registeredDeflectors:Vector.<ClassRegistryEntry>;
		/** @private */
		engine static var _registeredBehaviorTriggers:Vector.<ClassRegistryEntry>;
		/** @private */
		engine static var _registeredBehaviors:Vector.<ClassRegistryEntry>;
		
		
		//id accessors
		//------------------------------------------------------------------------------------------------
		
		public static function getParticleByID(id:String):Particle { return _particles[id]; }
		public static function getSourceByID(id:String):ParticleSource { return _sources[id]; }
		public static function getEmitterByID(id:String):Emitter { return _emitters[id]; }
		public static function getFieldByID(id:String):Field { return _fields[id]; }
		public static function getDeflectorByID(id:String):Deflector { return _deflectors[id]; }
		public static function getBehaviorTriggerByID(id:String):BehaviorTrigger { return _behaviorTriggers[id]; }
		public static function getBehaviorByID(id:String):Behavior { return _behaviors[id]; }
		
		//------------------------------------------------------------------------------------------------
		//end of ID accessors
		
		
		//get-all accessors
		//------------------------------------------------------------------------------------------------
		
		public static function getAllParticles():Vector.<Particle> {
			var vec:Vector.<Particle> = new Vector.<Particle>();
			for each (var p:Particle in _particles) vec.push(p);
			return vec;
		}
		public static function getAllSources():Vector.<ParticleSource> {
			var vec:Vector.<ParticleSource> = new Vector.<ParticleSource>();
			for each (var s:ParticleSource in _sources) vec.push(s);
			return vec;
		}
		public static function getAllEmitters():Vector.<Emitter> {
			var vec:Vector.<Emitter> = new Vector.<Emitter>();
			for each (var e:Emitter in _particles) vec.push(e);
			return vec;
		}
		public static function getAllFields():Vector.<Field> {
			var vec:Vector.<Field> = new Vector.<Field>();
			for each (var f:Field in _particles) vec.push(f);
			return vec;
		}
		public static function getAllDeflectors():Vector.<Deflector> {
			var vec:Vector.<Deflector> = new Vector.<Deflector>();
			for each (var d:Deflector in _particles) vec.push(d);
			return vec;
		}
		public static function getAllBehaviorTriggers():Vector.<BehaviorTrigger> {
			var vec:Vector.<BehaviorTrigger> = new Vector.<BehaviorTrigger>();
			for each (var bt:BehaviorTrigger in _particles) vec.push(bt);
			return vec;
		}
		public static function getAllBehaviors():Vector.<Behavior> {
			var vec:Vector.<Behavior> = new Vector.<Behavior>();
			for each (var b:Behavior in _particles) vec.push(b);
			return vec;
		}
		
		//------------------------------------------------------------------------------------------------
		//end of get-all accessors
		
		
		//IEmitterDestructible
		//------------------------------------------------------------------------------------------------
		
		/**
		 * If an <code>IEmitterDestructible</code> object will never be used in the future, 
		 * it should be marked as destroyed to release memory.
		 * Call this method and pass in the object you wish to mark as destroyed.
		 * @param	obj
		 */
		public static function destroy(obj:IEmitterDestructible):void {
			
			if (obj is Particle) {
				var p:Particle = obj as Particle;
				p._destroyed = true;
				delete _particles[p._id];
			} else if (obj is ParticleSource) {
				var s:ParticleSource = obj as ParticleSource;
				s._destroyed = true;
				delete _sources[s._id];
			} else if (obj is Emitter) {
				var emitter:Emitter = obj as Emitter;
				emitter._destroyed = true;
				delete _emitters[emitter._id];
			} else if (obj is Field) {
				var f:Field = obj as Field;
				f._destroyed = true;
				delete _fields[f._id];
			} else if (obj is Deflector) {
				var d:Deflector = obj as Deflector;
				d._destroyed = true;
				delete _deflectors[d._id];
			} else if (obj is Behavior) {
				var b:Behavior = obj as Behavior;
				b._destroyed = true;
				delete _behaviors[b._id];
			} else if (obj is BehaviorTrigger) {
				var bt:BehaviorTrigger = obj as BehaviorTrigger;
				bt._destroyed = true;
				delete _behaviorTriggers[bt._id];
			}
		}
		
		engine static function throwDestroyedObjectError(obj:IEmitterDestructible):void {
			throw new DestroyedObjectError(obj);
		}
		
		//------------------------------------------------------------------------------------------------
		//end of IEmitterDestructible
		
		
		//XML
		//------------------------------------------------------------------------------------------------
		
		private static function populateDefaultClassRegister():void {
			_registeredSources = new Vector.<ClassRegistryEntry>();
			_registeredFields = new Vector.<ClassRegistryEntry>();
			_registeredDeflectors = new Vector.<ClassRegistryEntry>();
			_registeredBehaviorTriggers = new Vector.<ClassRegistryEntry>();
			_registeredBehaviors = new Vector.<ClassRegistryEntry>();
			
			registerElementClass(EmitterElement.SOURCE, ParticleSourceType.POINT, PointSource);
			registerElementClass(EmitterElement.SOURCE, ParticleSourceType.LINE, LineSource);
			registerElementClass(EmitterElement.SOURCE, ParticleSourceType.CIRCLE, CircleSource);
			registerElementClass(EmitterElement.SOURCE, ParticleSourceType.RECT, RectSource);
			
			registerElementClass(EmitterElement.FIELD, FieldType.UNIFORM, UniformField);
			registerElementClass(EmitterElement.SOURCE, FieldType.RADIAL, RadialField);
			
			registerElementClass(EmitterElement.DEFLECTOR, DeflectorType.LINE, LineDeflector);
			registerElementClass(EmitterElement.DEFLECTOR, DeflectorType.CIRCLE, CircleDeflector);
			
			registerElementClass(EmitterElement.BEHAVIOR_TRIGGER, BehaviorTriggerType.BIRTH, BirthTrigger);
			registerElementClass(EmitterElement.BEHAVIOR_TRIGGER, BehaviorTriggerType.DEATH, DeathTrigger);
			registerElementClass(EmitterElement.BEHAVIOR_TRIGGER, BehaviorTriggerType.CIRCLE, CircleTrigger);
			registerElementClass(EmitterElement.BEHAVIOR_TRIGGER, BehaviorTriggerType.RECT, RectTrigger);
			registerElementClass(EmitterElement.BEHAVIOR_TRIGGER, BehaviorTriggerType.LIFE, LifeTrigger);
			
			registerElementClass(EmitterElement.BEHAVIOR, BehaviorType.DEATH, DeathBehavior);
			registerElementClass(EmitterElement.BEHAVIOR, BehaviorType.SPAWN, SpawnBehavior);
		}
		/**
		 * In order to make the <code>EmitterGlobal.toXML()</code> and <code>EmitterGlobal.parseXML()</code> methods work properly for custom element classes, 
		 * a custom element class must be registered through this method.
		 * @param	elementType  Type of the element class. See the <code>EmitterElement</code> class for constants.
		 * @param	type         The <code>type</code> attribute to be used in the element's XML representation. This value should be unique for each element type.
		 * @param	constructor  The element class object reference.
		 */
		public static function registerElementClass(elementType:int, type:String, constructor:Class):void {
			if (_registeredSources == null) populateDefaultClassRegister();
			
			var entry:ClassRegistryEntry;
			
			switch (elementType) {
				case EmitterElement.SOURCE:
					for each (entry in _registeredSources) {
						if (entry._type == type) throw new IllegalOperationError("Element type already registered.");
					}
					_registeredSources.push(new ClassRegistryEntry(type, constructor));
					break;
				case EmitterElement.FIELD:
					for each (entry in _registeredFields) {
						if (entry._type == type) throw new IllegalOperationError("Element type already registered.");
					}
					_registeredFields.push(new ClassRegistryEntry(type, constructor));
					break;
				case EmitterElement.DEFLECTOR:
					for each (entry in _registeredDeflectors) {
						if (entry._type == type) throw new IllegalOperationError("Element type already registered.");
					}
					_registeredDeflectors.push(new ClassRegistryEntry(type, constructor));
					break;
				case EmitterElement.BEHAVIOR_TRIGGER:
					for each (entry in _registeredBehaviorTriggers) {
						if (entry._type == type) throw new IllegalOperationError("Element type already registered.");
					}
					_registeredBehaviorTriggers.push(new ClassRegistryEntry(type, constructor));
					break;
				case EmitterElement.BEHAVIOR:
					for each (entry in _registeredBehaviors) {
						if (entry._type == type) throw new IllegalOperationError("Element type already registered.");
					}
					_registeredBehaviors.push(new ClassRegistryEntry(type, constructor));
					break;
				default:
					throw new IllegalOperationError("Invalid element type is specified.");
					break;
			}
		}
		/** @private */
		engine static function get xmlBase():XML {
			if (_registeredSources == null) populateDefaultClassRegister();
			
			var xml:XML = <emitterEngine/>;
			xml.@version = Emitter.VERSION;
			return xml;
		}
		/**
		 * Get the XML representation of all elements ever created.
		 * @return
		 */
		public static function toXML():XML {
			if (_registeredSources == null) populateDefaultClassRegister();
			
			var b:Behavior;
			var bt:BehaviorTrigger;
			var f:Field;
			var d:Deflector;
			var p:Particle;
			var s:ParticleSource;
			var emitter:Emitter;
			
			var xml:XML = xmlBase;
			
			for each (p in _particles) xml.appendChild(p.toXML());
			for each (s in _sources) xml.appendChild(s.toXML());
			for each (emitter in _emitters) xml.appendChild(emitter.toXML());
			for each (f in _fields) xml.appendChild(f.toXML());
			for each (d in _deflectors) xml.appendChild(d.toXML());
			for each (bt in _behaviorTriggers) xml.appendChild(bt.toXML());
			for each (b in _behaviors) xml.appendChild(b.toXML());
			
			return xml;
		}
		/**
		 * Parse XML representation of elements. Each node shall be an XML representation of an element.
		 * <p>
		 * Note there is one difference between the <code>EmitterGlobal.parseXML()</code> method and an element's <code>parseXML()</code> method.
		 * The former reads the <code>id</code> attribute in the XML representation and assigns it to an element, 
		 * where the latter simply ignores the <code>id</code> attribute.
		 * </p>
		 * @param	xml
		 */
		public static function parseXML(xml:XML):void {
			if (_registeredSources == null) populateDefaultClassRegister();
			
			var c:Class;
			var node:XML;
			var fakeID:String = "fakeID";
			
			var pVec:Vector.<Particle> = new Vector.<Particle>();
			var sVec:Vector.<ParticleSource> = new Vector.<ParticleSource>();
			var eVec:Vector.<Emitter> = new Vector.<Emitter>();
			var fVec:Vector.<Field> = new Vector.<Field>();
			var dVec:Vector.<Deflector> = new Vector.<Deflector>();
			var btVec:Vector.<BehaviorTrigger> = new Vector.<BehaviorTrigger>();
			var bVec:Vector.<Behavior> = new Vector.<Behavior>();
			
			var p:Particle;
			var s:ParticleSource;
			var emitter:Emitter;
			var f:Field;
			var d:Deflector;
			var bt:BehaviorTrigger;
			var b:Behavior;
			
			//instantiating objects & assigning IDs
			for each (node in xml.particle) {
				p = new Particle();
				try {
					p.id = node.@id.toString();
				} catch (e:Error) {}
				pVec.push(p);
			}
			for each (node in xml.source) {
				c = getConstructor(_registeredSources, node.@type.toString());
				s = new c() as ParticleSource;
				try {
					s.id = node.@id.toString();
				} catch (e:Error) {}
				sVec.push(s);
			}
			for each (node in xml.emitter) {
				emitter = new Emitter();
				try {
					emitter.id = node.@id.toString();
				} catch (e:Error) {}
				eVec.push(emitter);
			}
			for each (node in xml.field) {
				c = getConstructor(_registeredFields, node.@type.toString());
				f = new c() as Field;
				try {
					f.id = node.@id.toString();
				} catch (e:Error) {}
				fVec.push(f);
			}
			for each (node in xml.deflector) {
				c = getConstructor(_registeredDeflectors, node.@type.toString());
				d = new c() as Deflector;
				try {
					d.id = node.@id.toString();
				} catch (e:Error) {}
				dVec.push(d);
			}
			for each (node in xml.behaviorTrigger) {
				c = getConstructor(_registeredBehaviorTriggers, node.@type.toString());
				bt = new c() as BehaviorTrigger;
				try {
					bt.id = node.@id.toString();
				} catch (e:Error) {}
				btVec.push(bt);
			}
			for each (node in xml.behavior) {
				c = getConstructor(_registeredBehaviors, node.@type.toString());
				b = new c() as Behavior;
				try {
					b.id = node.@id.toString();
				} catch (e:Error) {}
				bVec.push(b);
			}
			
			//parsing element XMLs
			var i:int;
			i = 0;
			for each (node in xml.particle) {
				pVec[i].parseXML(node);
				i++;
			}
			i = 0;
			for each (node in xml.source) {
				sVec[i].parseXML(node);
				i++;
			}
			i = 0;
			for each (node in xml.emitter) {
				eVec[i].parseXML(node);
				i++;
			}
			i = 0;
			for each (node in xml.field) {
				fVec[i].parseXML(node);
				i++;
			}
			i = 0;
			for each (node in xml.deflector) {
				dVec[i].parseXML(node);
				i++;
			}
			i = 0;
			for each (node in xml.behaviorTrigger) {
				btVec[i].parseXML(node);
				i++;
			}
			i = 0;
			for each (node in xml.behavior) {
				bVec[i].parseXML(node);
				i++;
			}
		}
		private static function getConstructor(register:*, type:String):Class {
			for each (var entry:ClassRegistryEntry in register) {
				if (entry._type == type) return entry._class;
			}
			throw new IllegalOperationError("Element type not registered: " + type);
		}
		
		//------------------------------------------------------------------------------------------------
		//end of XML
	}
}